package com.cf.expensetracker;

import com.cf.expensetracker.command.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.TextStyle;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * Copyright(C) 2025- com.cf
 * FileName:    HandleCmd
 * Author:      cf
 * Date:        2025/1/21 22:03
 * Description: Handle command
 */
public class HandleCmd {
    private static final String FILENAME = "expense-data.json";

    /**
     * Handel subCmd
     * @param subCmd
     */
    public static void handle(Object subCmd) {
        if (subCmd instanceof AddCmd) { // add command
            handleAddCmd((AddCmd) subCmd);
        } else if (subCmd instanceof ListCmd) { // list command
            handleListCmd((ListCmd) subCmd);
        } else if (subCmd instanceof DeleteCmd) { // delete command
            handleDeleteCmd((DeleteCmd) subCmd);
        } else if (subCmd instanceof SummaryCmd) { // summary command
            handleSummaryCmd((SummaryCmd) subCmd);
        } else if (subCmd instanceof UpdateCmd) { // update command
            handleUpdateCmd((UpdateCmd) subCmd);
        } else if (subCmd instanceof ExportCmd) { // export command
            handleExportCmd();
        } else if (subCmd instanceof BudgetCmd) { // budget command
            handleBudgetCmd((BudgetCmd) subCmd);
        }
    }

    /**
     * Add command
     * @param addCmd
     */
    private static void handleAddCmd(AddCmd addCmd) {
        // Validate amount
        addCmd.validate();

        // A new expense entity
        Expense expense = new Expense();
        expense.setDescription(addCmd.getDescription());
        expense.setAmount(addCmd.getAmount());
        expense.setCreatedAt(LocalDate.now());

        if (addCmd.getCategory() != null) {
            expense.setCategory(addCmd.getCategory());
        }

        // Read from a json file
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);

        expense.setId(result.getMaxId());
        List<Expense> expenses = result.getExpenses();
        expenses.add(expense);

        // Update current maxId
        result.setMaxId(result.getMaxId() + 1);

        // Write to json file
        ExpenseFileUtil.writeToJsonFile(FILENAME, result);

        System.out.println("# Expense added successfully (ID: " + expense.getId() + ")");

        Double budget = result.getBudget();
        // Compute total amount for current month
        Double total = expenses.stream()
                .filter(ep -> ep.getCreatedAt().getMonth().equals(LocalDate.now().getMonth()))
                .map(Expense::getAmount)
                .reduce(Double::sum).orElseThrow();

        if (budget != -1.0 && budget < total) {
            System.out.println("# Warning, you exceed budget: $" + budget);
        }
    }

    /**
     * List command
     * @param listCmd
     */
    private static void handleListCmd(ListCmd listCmd) {
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        List<Expense> expenses = result.getExpenses();
        if (expenses != null && !expenses.isEmpty()) {
            List<Expense> collect;
            if (listCmd.getCategory() != null) {
                collect = expenses.stream()
                        .filter(expense -> listCmd.getCategory().equals(expense.getCategory()))
                        .collect(Collectors.toList());
            } else {
                collect = expenses;
            }
            if (!collect.isEmpty()) {
                System.out.println("# ID\tDate\t\tDescription\tAmount\t\tCategory");
                collect.forEach(expense -> {
                    System.out.println("# " + expense.getId() + " \t"
                            + expense.getCreatedAt() + "\t"
                            + expense.getDescription() + "\t\t"
                            + expense.getAmount() + "\t\t"
                            + (expense.getCategory() != null ? expense.getCategory() : ""));
                });
            }
        }
    }

    /**
     * Delete command
     * @param deleteCmd
     */
    private static void handleDeleteCmd(DeleteCmd deleteCmd) {
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        List<Expense> expenses = result.getExpenses();


        // Validate id
        deleteCmd.validate();
        Integer id = deleteCmd.getId();

        if (expenses != null && !expenses.isEmpty()) {
            boolean exist = expenses.stream().anyMatch(expense -> expense.getId().equals(id));
            if (!exist) {
                System.out.printf("# ID [%d] not exist.%n", id);
                return;
            }

            List<Expense> collect = expenses.stream()
                    .filter(expense -> !expense.getId().equals(id))
                    .toList();
            result.setExpenses(collect);
            ExpenseFileUtil.writeToJsonFile(FILENAME, result);
            System.out.println("# Expense deleted successfully");
        } else {
            System.out.println("# No expense to delete");
        }
    }

    /**
     * Summary command
     * @param summaryCmd
     */
    private static void handleSummaryCmd(SummaryCmd summaryCmd) {
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        List<Expense> expenses = result.getExpenses();
        if (expenses != null && !expenses.isEmpty()) {

            List<Expense> collect;
            Integer month = summaryCmd.getMonth();
            // expense-tracker summary
            if (month == null) {
                collect = expenses;
                // Compute total amount of expense
                double total = 0;
                for (Expense expense : collect) {
                    total += expense.getAmount();
                }
                System.out.println("# Total expenses: $" + total);
            } else { // expense-tracker summary --month 1
                // Filter a specific month of current year
                // Validate --month
                summaryCmd.validate();
                collect = expenses.stream()
                        .filter(
                                expense -> expense.getCreatedAt().getMonth().getValue() == month)
                        .collect(Collectors.toList());

                // Compute total amount of expense
                double total = 0;
                for (Expense expense : collect) {
                    total += expense.getAmount();
                }
                System.out.println("# Total expenses for "
                        + Month.of(month).getDisplayName(TextStyle.FULL, Locale.ENGLISH) + ": $" + total);
            }
        } else {
            System.out.println("# No expense to summary");
        }
    }

    /**
     * Update command
     * @param updateCmd
     */
    private static void handleUpdateCmd(UpdateCmd updateCmd) {
        Integer id = updateCmd.getId();

        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        List<Expense> expenses = result.getExpenses();

        if (expenses != null && !expenses.isEmpty()) {
            boolean exist = expenses.stream().anyMatch(expense -> expense.getId().equals(id));
            if (!exist) {
                System.out.printf("# ID [%d] not exist.%n", id);
                return;
            }
            // Validate amount
            updateCmd.validate();
            // Update expense
            Expense expense = new Expense();
            expense.setId(id);
            expense.setAmount(updateCmd.getAmount());
            expense.setDescription(updateCmd.getDescription());
            expense.setCreatedAt(LocalDate.now());

            if (updateCmd.getCategory() != null) {
                expense.setCategory(updateCmd.getCategory());
            }

            List<Expense> collect = expenses.stream()
                    .filter(ep -> !ep.getId().equals(id))
                    .collect(Collectors.toList());
            collect.add(expense);
            result.setExpenses(collect);
            ExpenseFileUtil.writeToJsonFile(FILENAME, result);
            System.out.println("# Expense updated successfully");
        } else {
            System.out.println("# No expense to update");
        }
    }

    /**
     * Export command
     */
    private static void handleExportCmd() {
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        List<Expense> expenses = result.getExpenses();
        Path filename = Path.of("export-data.csv");
        if (expenses != null && !expenses.isEmpty()) {
            try {
                // Delete if filename exist
                if (filename.toFile().exists()) {
                    Files.delete(filename);
                }

                StringBuilder content = new StringBuilder();
                for (Expense expense : expenses) {
                    String ep = expense.getId() + "," + expense.getCreatedAt() + ","
                            + expense.getDescription() + "," + expense.getAmount();
                    if (expense.getCategory() != null) {
                        ep += "," + expense.getCategory();
                    }
                    content.append(ep);
                    content.append("\n");
                }

                // Export data to csv file
                Files.writeString(
                        filename,
                        "ID,Date,Description,Amount,Category\n" + content,
                        StandardOpenOption.CREATE);
                System.out.println("# Expenses exported successfully");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            System.out.println("# No expense to export");
        }
    }

    /**
     * Budget command
     * @param budgetCmd
     */
    private static void handleBudgetCmd(BudgetCmd budgetCmd) {
        budgetCmd.validate();
        Result result = ExpenseFileUtil.readFromJsonfile(FILENAME);
        result.setBudget(budgetCmd.getAmount());
        ExpenseFileUtil.writeToJsonFile(FILENAME, result);
        System.out.println("# Budget set successfully");
    }
}
